package idplib

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/StackExchange/wmi"
	psnet "github.com/shirou/gopsutil/v3/net"
	"github.com/urfave/cli/v2"
	"miao-client-plugins/utils"
	"net"
	"os"
	"strings"
)

type IdpServicePlugin struct {
}

func (p IdpServicePlugin) SayHi() {
	fmt.Println("idp service plugin say hi")
}
func NewIdpServicePlugin() IdpServicePlugin {
	return IdpServicePlugin{}
}

const (
	MainBoardWql = "SELECT HostingBoard,HotSwappable,Manufacturer,SerialNumber FROM Win32_BaseBoard "
	BiosWql      = "SELECT SerialNumber,Manufacturer FROM Win32_BIOS"
	DiskWql      = "SELECT InterfaceType,MediaType,SerialNumber,Size FROM Win32_DiskDrive"
)

const miaoClientPath = "C:\\VEIDP\\idpClient.exe"

var (
	BuildTime string
	Version   = "1.0.0"
)

func Do() {

	app := cli.NewApp()
	plu := NewIdpServicePlugin()
	app.Name = "idp-handsUp-cli"
	app.Version = fmt.Sprintf("%s build:%s", Version, BuildTime)
	app.Usage = "为 idp 举手指令提供数据支持,包括：主板序列号、bios序列号、硬盘的序列号、Hostname、当前的IP和IP对应的活跃MAC地址"
	app.UsageText = "eg: hands_up.exe -isSystemDisk=true 获取举手信息，基于操作系统所在的分区\n "
	app.Flags = []cli.Flag{
		&cli.StringFlag{
			Name:        "path",
			Aliases:     []string{"p"},
			Usage:       "指定主程序所在的路径，未指定的情况下默认使用路径",
			DefaultText: miaoClientPath,
		},
		&cli.BoolFlag{
			Name:    "isSystemDisk",
			Aliases: []string{"c"},
			Usage:   "硬盘序列号选取系统系统分区的那个物理分区，true-选择系统所在的分区；false-选择自然序列中的第一个分区",
		},
	}
	app.Action = func(ctx *cli.Context) error {
		var path = ctx.String("path")
		if path == "" {
			path = miaoClientPath
		}
		isC := ctx.Bool("isSystemDisk")
		var (
			info HandsUpMsg
			err  error
			bs   []byte
		)
		if info, err = plu.GetHandsUpInfo(path, isC); err != nil {
			return cli.Exit(fmt.Sprintf("handsUp err:%s", err), 1)
		}
		if bs, err = json.Marshal(info); err != nil {
			return cli.Exit(fmt.Sprintf("handsUp err:%s", err), 1)
		}
		w := bufio.NewWriter(os.Stdout)
		_, _ = w.WriteString(string(bs) + "\n")
		_ = w.Flush()
		return nil
	}
	if err := app.Run(os.Args); err != nil {
		fmt.Printf("命令行调用错误：%s ，请查看帮助文档", err.Error())
	}
}

type Board struct {
	HostingBoard bool   `json:"hostingBoard,omitempty"` // eg:TRUE,如果为TRUE，该卡是一个主板，或在一个机箱中的基板。
	HotSwappable bool   `json:"hotSwappable,omitempty"` // eg:FALSE,如果为TRUE，就是支持热插拔（判断是否支持热插拔）
	Manufacturer string `json:"manufacturer,omitempty"` // eg:HASEE Computer,表示制造商的名称
	SerialNumber string `json:"serialNumber,omitempty"` // eg:00000000 ，经过测试大品牌机器这个值都是有唯一值的，我的是杂牌电脑 .制造商分配的用于识别所述物理元件数目。
}

// Bios for Win32_BIOS
type Bios struct {
	SerialNumber string `json:"serialNumber"` // eg:EJVPM21124000449 , 序列号
	Manufacturer string `json:"manufacturer"` // eg:HUAWEI , 制造商
}

type PhysicsDiskInfo struct {
	InterfaceType string `json:"interfaceType,omitempty"` // eg:SCSI ,物理磁盘驱动器的类型 （IDE、sata）
	MediaType     string `json:"mediaType,omitempty"`     // eg:Fixed hard disk media ,由该设备使用或访问的媒体类型。
	SerialNumber  string `json:"serialNumber,omitempty"`  // eg:6479_A721_8068_6489. 由制造商分配的号来识别物理介质。
	Size          uint64 `json:"size,omitempty"`          // eg:256052966400 ,磁盘大小 单位字节 / 1024*1024*1024 才能得到G，
}

func (p IdpServicePlugin) getMainBoardSerialNum() (boardSerNum, biosSerNum string, err error) {
	var board []Board
	var bios []Bios
	if err = wmi.Query(MainBoardWql, &board); err != nil {
		return
	}
	if err = wmi.Query(BiosWql, &bios); err != nil {
		return
	}

	for _, v := range board {
		if v.HostingBoard {
			boardSerNum = v.SerialNumber
		}
	}
	if len(bios) > 0 {
		biosSerNum = bios[0].SerialNumber
	}
	return
}

func (p IdpServicePlugin) getHardDiskSerialNum() (serial string, err error) {
	var data []PhysicsDiskInfo
	if err = wmi.Query(DiskWql, &data); err != nil {
		return "", err
	}
	for index := range data {
		data[index].SerialNumber = strings.TrimSpace(data[index].SerialNumber)
		if data[index].Size == 0 {
			data = append(data[:index], data[index+1:]...)
		}
	}
	for _, v := range data {
		// https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskdrive?redirectedfrom=MSDN
		// 关于 MediaType 的描述，只能做这种猜想，并不能确定，因为它不是一个枚举类型
		if strings.Contains(v.MediaType, "hard disk") {
			return v.SerialNumber, nil
		}
	}
	if len(data) > 0 {
		return data[0].SerialNumber, nil
	}
	return "", nil
}

type DiskPart struct {
	Bootable      bool   `json:"bootable,omitempty"`      // 指示计算机是否可以从此分区启动。
	BootPartition bool   `json:"bootPartition,omitempty"` // 分区是活动分区。 从硬盘启动时，操作系统使用活动分区。
	DiskIndex     uint32 `json:"diskIndex,omitempty"`     // 分区的磁盘的索引号
	Index         uint32 `json:"index,omitempty"`         // 分区的索引号
}

// getSystemDiskSerial 获取系统盘所在的物理磁盘的序列号
func (p IdpServicePlugin) getSystemDiskSerial() (serial string, err error) {
	var parts []DiskPart
	if err = wmi.Query(" SELECT * FROM Win32_DiskPartition ", &parts); err != nil {
		return "", err
	}
	var diskIndex uint32
	for _, item := range parts {
		if item.Bootable && item.BootPartition {
			diskIndex = item.DiskIndex
		}
	}
	var disks []PhysicsDiskInfo
	if err = wmi.Query(" SELECT * FROM Win32_DiskDrive ", &disks); err != nil {
		return "", err
	}
	if diskIndex >= uint32(len(disks)) {
		return "", errors.New("超出下表范围")
	}
	serial = disks[diskIndex].SerialNumber
	return serial, nil
}

// getIpMac 获取当前活跃ipv4地址和其对应的网卡地址
func (p IdpServicePlugin) getIpMac() (ip, mac string, err error) {
	var (
		psn psnet.InterfaceStatList
	)
	if ip, err = getOutBoundIP(); err != nil {
		return
	}
	if psn, err = psnet.Interfaces(); err != nil {
		return
	}
	for _, item := range psn {
		if len(item.Addrs) == 2 {
			ipv4 := item.Addrs[1].Addr
			ipv4 = ipv4[:strings.LastIndex(ipv4, "/")]
			if ipv4 == ip {
				mac = item.HardwareAddr
				mac = strings.ReplaceAll(mac, ":", "-")
			}
		}
	}
	return
}

func getOutBoundIP() (ip string, err error) {
	var (
		conn net.Conn
	)
	conn, err = net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		return "", err
	}
	localAdd := conn.LocalAddr()
	ip = strings.Split(localAdd.String(), ":")[0]
	return ip, nil
}

func (p IdpServicePlugin) GetHandsUpInfo(mainPath string, isC bool) (info HandsUpMsg, err error) {
	var (
		ip        string
		mac       string
		bios      string
		mainBoard string
		disk      string
		hostname  string
		md5       string
	)
	if ip, mac, err = p.getIpMac(); err != nil {
		return
	}
	if mainBoard, bios, err = p.getMainBoardSerialNum(); err != nil {
		return
	}
	if isC {
		if disk, err = p.getSystemDiskSerial(); err != nil {
			return
		}
	} else {
		if disk, err = p.getHardDiskSerialNum(); err != nil {
			return
		}
	}
	if md5, err = utils.GetFileMd5(mainPath); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			err = errors.New("指定的文件不存在")
		}
		return
	}
	if hostname, err = os.Hostname(); err != nil {
		return
	}
	info.IP = ip
	info.ActiveMAC = mac
	info.Md5 = md5
	info.HardDiskSerial = disk
	info.MainBoardSerial = mainBoard
	info.BiosSerial = bios
	info.Hostname = hostname
	return
}
